package aufgabe2.worker;
/**
 * This class is used to find a factor to the number toFactorize with the Rho Method. 
 * When one is found, it is sent to the master
 */


import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Random;

import akka.actor.ActorRef;

public class Treatment implements Runnable {
	public BigInteger toFactorize;
	private CalculateMessage calculateMessage;
	private ActorRef master;
	private int id;
	public int sumrho=0;
	public long time=0;
	public static ArrayList<BigInteger> rlist = new ArrayList<BigInteger>();
	public boolean ended=false; //the factorization isn't terminated
	private boolean stop = false;
	
	private final static BigInteger ZERO = new BigInteger("0");
	private final static BigInteger ONE  = new BigInteger("1");
	private final static BigInteger TWO  = new BigInteger("2");
	
	public Treatment(CalculateMessage messReceived, int id, ActorRef master)
	{
		this.calculateMessage = messReceived;
		this.toFactorize = calculateMessage.getNumber();
		this.master = master;
		this.id = id;
	}

	@Override
	public void run() {
		try{
			factor();
		}catch(Exception e){
		}
		if(ended) //if factorization is finished
		{
			System.out.println("Result: " + rlist); 
			ResultMessage resultMessage = new ResultMessage(toFactorize, rlist, time, sumrho);
			resultMessage.setEnded(true);
			this.stop = true;
			//send the list with all the factors to the master.
			master.tell(resultMessage);
			rlist=new ArrayList<BigInteger>();
		}
		
	}
	
	public BigInteger rho(BigInteger nbr, BigInteger bi) {
        BigInteger dvr=bi;
        BigInteger x;

        // check divisibility by 2
        if (nbr.mod(TWO).compareTo(ZERO) == 0) return TWO;

        do {
            bi  =  bi.pow(2).mod(nbr).add(BigInteger.ONE).mod(nbr);
            dvr = dvr.pow(2).mod(nbr).add(BigInteger.ONE).mod(nbr);
            dvr = dvr.pow(2).mod(nbr).add(BigInteger.ONE).mod(nbr);
            x = (dvr.subtract(bi)).abs().gcd(nbr);
            sumrho++;
            if (x.compareTo(BigInteger.ONE) != 0 && x.compareTo(nbr) !=0) {
            	x = rho(x, nextRandomBigInteger(x)); 
				return x;
			}
            if (x.compareTo(nbr) ==0 ) {
				return nbr;
			}    
        } while(((x.compareTo(ONE)) == 0) && (!stop));
        
        if(!stop){
        	System.out.println(nbr);
        	return nbr;
        }
        else //returns null when the factorization has been stoped
        	return null;
    }
	
	public void factor() {
    	long beginTime=System.currentTimeMillis();
        BigInteger divisor = rho(toFactorize,nextRandomBigInteger(toFactorize));
        if(divisor != null) //factorization was not stopped
        {
        	rlist.add(divisor);
	        toFactorize=toFactorize.divide(divisor);
	        if (toFactorize.compareTo(ONE) == 0)
	        {
	        	ended=true;
	        }
	        else {
	        	ResultMessage msg = new ResultMessage();
				msg.setPrimeFactor(rlist);
				msg.setToFactorize(divisor);
				msg.setSumRhoCycle(0);
				msg.setEnded(false);
				msg.setCalculId(id);
				master.tell(msg);
	        }
        }
        else //factorization was stopped
        {
        	TerminateMessage msg = new TerminateMessage();
        	msg.sumrho = sumrho;
			msg.cpuTime=time;
			master.tell(msg);
			ended = true;
        }
        
        time = time+System.currentTimeMillis()-beginTime;
    }

    public static BigInteger nextRandomBigInteger(BigInteger r) {
		Random rand = new Random();
		BigInteger result = new BigInteger(r.bitLength(), rand);
		while (result.compareTo(r) >= 0) {
			result = new BigInteger(r.bitLength(), rand);
		}
		return result;
	}

	public void stop() {
		System.out.println("Factorization was stopped");
		this.stop = true;
		
	}

}
